﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using Notoric.Model.Internal;
using Notoric.Model.Packaging;
using Notoric.Model.Validation;

namespace Notoric.Model.Elements.CWNotation
{
    /// <summary>
    /// Abstract base class for environment modifiers.
    /// </summary>
    public abstract class EnvironmentModifier : CoreSymbol
    {
        #region Constructors

        /// <summary>
        /// Creates a new <see cref="EnvironmentModifier"/> instance.
        /// </summary>
        /// <param name="packagePart">
        /// <para>
        /// Type: <see cref="XmlPackagePart"/>
        /// </para>
        /// <para>
        /// The package part that owns this element.
        /// </para>
        /// </param>
        /// <param name="owner">
        /// <para>
        /// Type: <see cref="Staff"/>
        /// </para>
        /// <para>
        /// The model element that directly owns this element.
        /// </para>
        /// </param>
        /// <param name="measure">
        /// <para>
        /// Type: <see cref="MeasureElement"/>
        /// </para>
        /// <para>
        /// The measure that this symbol logically belongs to.
        /// </para>
        /// </param>
        /// <param name="beat">
        /// <para>
        /// Type: <see cref="BeatElement"/>
        /// </para>
        /// <para>
        /// The beat that this symbol logically belongs to.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="packagePart"/> or <paramref name="owner"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ValidationException">
        /// The <paramref name="measure"/> or <paramref name="beat"/> is <see langword="null"/>.
        /// </exception>
        internal EnvironmentModifier(XmlPackagePart packagePart, Staff owner, MeasureElement measure, BeatElement beat)
            : base(packagePart, owner)
        {
            if (owner == null)
                throw new ArgumentNullException("owner");

            Measure = measure;
            Beat = beat;
            validBeforeBeatAttribute.Value = true.ToString();
        }

        /// <summary>
        /// Creates a new <see cref="EnvironmentModifier"/> instance.
        /// </summary>
        /// <param name="packagePart">
        /// <para>
        /// Type: <see cref="XmlPackagePart"/>
        /// </para>
        /// <para>
        /// The package part that owns this element.
        /// </para>
        /// </param>
        /// <param name="owner">
        /// <para>
        /// Type: <see cref="Staff"/>
        /// </para>
        /// <para>
        /// The model element that directly owns this element.
        /// </para>
        /// </param>
        /// <param name="element">
        /// <para>
        /// Type: <see cref="XmlElement"/>
        /// </para>
        /// <para>
        /// The XML element that holds the data of this object.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// Any of the arguments is <see langword="null"/>.
        /// </exception>
        internal EnvironmentModifier(XmlPackagePart packagePart, Staff owner, XmlElement element)
            : base(packagePart, owner, element)
        {
            if (owner == null)
                throw new ArgumentNullException("owner");

            beatAttribute = element.Attributes[PackageVocabulary.BeatAttribute];
            measureAttribute = element.Attributes[PackageVocabulary.MeasureAttribute];
            validBeforeBeatAttribute = element.Attributes[PackageVocabulary.ValidBeforeBeatAttribute];
            try
            {
                var measureNumber = ushort.Parse(measureAttribute.Value, PackageVocabulary.PackageCulture.NumberFormat);
                measure = FindOwner<MusicSectionElement>().Composition.Composition[measureNumber];
            }
            catch (Exception e)
            {
                throw new SerializationException("Could not retrieve reference to the measure number " + measureAttribute.Value + "!", element.OuterXml, e);
            }
            try
            {
                var part = measure[owner.Descriptor.FindOwner<PartDescriptorElement>()];
                var beg = ushort.Parse(beatAttribute.Value, PackageVocabulary.PackageCulture.NumberFormat);
                beat = part.Content[beg];
            }
            catch (Exception e)
            {
                throw new SerializationException("Could not retrieve reference to the beat starting at " + beatAttribute.Value + "!", element.OuterXml, e);
            }
        }

        #endregion

        #region CoreSymbol Members

        /// <inheritdoc/>
        protected override XmlElement CreateSymbolElement(XmlDocument document)
        {
            var element = CreateSymbolElementCore(document);

            measureAttribute = document.CreateAttribute(PackageVocabulary.MeasureAttribute);
            beatAttribute = document.CreateAttribute(PackageVocabulary.BeatAttribute);
            validBeforeBeatAttribute = document.CreateAttribute(PackageVocabulary.ValidBeforeBeatAttribute);

            element.Attributes.Append(measureAttribute);
            element.Attributes.Append(beatAttribute);
            element.Attributes.Append(validBeforeBeatAttribute);

            return element;
        }

        /// <inheritdoc/>
        public override BeatElement Beat
        {
            get { return beat; }
            set
            {
                ValidateAndThrow("Beat", value);

                beat = value;
                beatAttribute.Value = beat.Beginning.ToString(PackageVocabulary.PackageCulture.NumberFormat);

                RaisePropertyChanged("Beat");
            }
        }

        /// <inheritdoc/>
        public override MeasureElement Measure
        {
            get { return measure; }
            set
            {
                ValidateAndThrow("Measure", value);

                measureAttribute.Value = value.Number.ToString();
                measure = value;

                RaisePropertyChanged("Measure");
            }
        }

        #endregion

        #region Protected Methods

        /// <summary>
        /// When overriden in a derived class, this method creates the XML element
        /// that will hold the data of this object.
        /// </summary>
        /// <remarks>
        /// The attributes for the inherited properties and the type of the class
        /// are added by the base classes.
        /// </remarks>
        /// <param name="document"></param>
        /// <returns></returns>
        protected abstract XmlElement CreateSymbolElementCore(XmlDocument document);

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets or sets the value indicating whether this symbol is valid before or after its beat.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Boolean"/>
        /// </para>
        /// <para>
        /// Indicates whether this symbol is valid before (<see langword="true"/>) or after (<see langword="false"/>)
        /// its <see cref="CoreSymbol.Beat"/>. Default value is <see langword="true"/>.
        /// </para>
        /// <para>
        /// To be valid before its beat means, that it affects the environment of all the symbols on the same beat.
        /// Otherwise, the symbols on the same beat are not affected and the modifier starts to be valid for the first
        /// following symbol. This is useful to represent a change of the environment modifier (such as clef) on the beginning
        /// of the next system. In these cases a small clef or a cancelling key signature is usually drawn on the end of the staff
        /// that just informs the player about the change that will follow, but does not itself affect any symbol on its staff.
        /// </para>
        /// </value>
        public bool ValidBeforeBeat
        {
            get { return bool.Parse(validBeforeBeatAttribute.Value); }
            set
            {
                validBeforeBeatAttribute.Value = value.ToString();

                RaisePropertyChanged("ValidBeforeBeat");
                RaiseEnvironmentChanged();
            }
        }

        #endregion

        #region Private Fields

        /// <summary>
        /// Holds the value of the <see cref="CoreSymbol.Beat"/> property.
        /// </summary>
        private BeatElement beat;
        /// <summary>
        /// Holds the XML attribute that holds the value of the <see cref="CoreSymbol.Beat"/> property.
        /// </summary>
        private XmlAttribute beatAttribute;
        /// <summary>
        /// Holds the value of the <see cref="CoreSymbol.Measure"/> property.
        /// </summary>
        private MeasureElement measure;
        /// <summary>
        /// Holds the XML attribute that holds the value of the <see cref="CoreSymbol.Measure"/> property.
        /// </summary>
        private XmlAttribute measureAttribute;
        /// <summary>
        /// Holds the XML attribute that holds the value of the <see cref="ValidBeforeBeat"/> property.
        /// </summary>
        private XmlAttribute validBeforeBeatAttribute;

        #endregion
    }
}
